Forum Discussion
Stacking The Beatles in Excel: An Exercise in 3D stacking
Well done!
I've been doing a lot of table transformations with INDEX-SEQUENCE algorithms recently, so I took a crack at it:
=LAMBDA(ref_3d,
LET(
a, HSTACK(ref_3d),
b, TAKE(a,1),
h, ROWS(a)-1,
w, COLUMNS(a),
m, SHEETS(ref_3d),
n, w/m,
k, SEQUENCE(h*m,,0),
x, DROP(SORTBY(a,QUOTIENT(SEQUENCE(,w,0),n),1,b,1),1),
y, INDEX(x,MOD(k,h)+1,QUOTIENT(k,h)*n+SEQUENCE(,n)),
VSTACK(UNIQUE(SORT(b,,,1),1),FILTER(y,TAKE(y,,1)<>""))
)
)
It's a little longer, but should hold up well performance-wise.
If any one of the worksheets were missing a field, though, I think an unpivot-repivot method might be the way to go. Something like:
=LET(
a, HSTACK(Albums3D),
b, DROP(a,1),
w, COLUMNS(a),
i, SEQUENCE(ROWS(b)),
j, QUOTIENT(SEQUENCE(,w,0),w/SHEETS(Albums3D)),
t, b<>"",
f, LAMBDA(x,TOCOL(IFS(t,x),2)),
DROP(PIVOTBY(HSTACK(f(j),f(i)),f(TAKE(a,1)),f(b),SINGLE,0,0,,0),,2)
)
Or:
=LET(
a, HSTACK(Albums3D),
b, DROP(a,1),
v, TOCOL(b),
w, COLUMNS(a),
i, SEQUENCE(ROWS(b)),
j, QUOTIENT(SEQUENCE(,w,0),w/SHEETS(Albums3D)),
f, LAMBDA(x,TOCOL(IF({1},x,b))),
DROP(PIVOTBY(HSTACK(f(j),f(i)),f(TAKE(a,1)),v,SINGLE,0,0,,0,,v<>""),,2)
)
I'm not really sure if there's any difference in efficiency between the two. I'd like to think the second one is faster because it removes the blank records in one swoop with the [filter_array] argument of PIVOTBY, whereas the first method removes the blank records for every instance of TOCOL (4 times), but I didn't run any tests with larger datasets.
Cheers!
Looks good! No reduction tricks needed in 2025. Creative use of PIVOTBY to aggregate as scalars with SINGLE.
In re: unpivot/re-pivot - Melt from Pandas might interest you. Could be re-created in Lambda certainly:
- djclementsAug 15, 2025Bronze Contributor
Interesting concept, passing the entire table as the first and only required argument, then unpivoting based on the optional arguments provided. Definitely looks doable as a lambda function.
I have a couple of custom functions already that I use for basic unpivots. The first one automatically removes blanks and errors, which is the most common request:
=LAMBDA(row_fields,col_labels,values,[scan_by_col], LET( tst, values<>"", fnλ, LAMBDA(arr,TOCOL(IFS(tst,arr),2,scan_by_col)), HSTACK( CHOOSEROWS(row_fields,fnλ(SEQUENCE(ROWS(row_fields)))), CHOOSEROWS(TRANSPOSE(col_labels),fnλ(SEQUENCE(,COLUMNS(col_labels)))), fnλ(values) ) ) )
Set the optional [scan_by_col] argument to 1 or TRUE to change the output order.
The second one has the option to remove blanks and errors as desired:
=LAMBDA(row_fields,col_labels,values,[ignore],[scan_by_col], LET( v, TOCOL(values,,scan_by_col), a, HSTACK( INDEX(row_fields,TOCOL(IFNA(SEQUENCE(ROWS(row_fields)),values),,scan_by_col),SEQUENCE(,COLUMNS(row_fields))), INDEX(col_labels,SEQUENCE(,ROWS(col_labels)),TOCOL(IFNA(SEQUENCE(,COLUMNS(col_labels)),values),,scan_by_col)),v), CHOOSE(ignore+1,a,FILTER(a,NOT(ISBLANK(v))),FILTER(a,NOT(ISERROR(v))),FILTER(a,NOT(ISBLANK(v)+ISERROR(v)))) ) )
The optional [ignore] argument accepts the same choices as that of TOCOL/TOROW:
- 0 - Keep all values (default)
- 1 - Ignore blanks
- 2 - Ignore errors
- 3 - Ignore blanks and errors
Both methods can handle multiple columns of row_fields, as well as multiple rows of col_labels.
In most cases I'm not too worried about headers as they're pretty easy to input/stack manually and customize on your own. If you wanted to get cute, though, and include an option for headers, it could look something like this:
=LAMBDA(row_fields,col_labels,values,[scan_by_col],[headers], LET( tst, values<>"", fnλ, LAMBDA(arr,TOCOL(IFS(tst,arr),2,scan_by_col)), pvt, HSTACK( CHOOSEROWS(row_fields,fnλ(SEQUENCE(ROWS(row_fields)))), CHOOSEROWS(TRANSPOSE(col_labels),fnλ(SEQUENCE(,COLUMNS(col_labels)))), fnλ(values) ), IF( TYPE(headers)=64, VSTACK(headers,pvt), IF( headers, VSTACK(HSTACK("Field"&SEQUENCE(,COLUMNS(row_fields)),"Attribute"&SEQUENCE(,ROWS(col_labels)),"Value"),pvt), pvt ) ) ) )
The optional [headers] argument would then accept the following:
- 0 or FALSE - None (default)
- 1 or TRUE - Auto-generate
- A horizontal array of custom headers, e.g. {"first","last","attribute","value"}
For example:
Unpivot with multiple row and column fields=Unpivotλ(A3:B5,C1:H2,C3:H5,TRUE,{"First","Last","Fiscal Year","Month","Commissions"})
More advanced scenarios involving multiple value fields (e.g. Sales and Commissions) can get kind of hairy, so I just end up writing tailored solutions on a case-by-case basis for those situations.
- Patrick2788Aug 15, 2025Silver Contributor
I like the first the one the best. It's very straightforward and efficient with the work mostly done with sequencing and some shaping functions. You may convince me to give IFS another look as it's never been one of my go-to functions. It is used smartly here in a way where the evaluating all arguments thing doesn't matter.
With the first option I don't mind keeping errors and blanks if the function is speedy. Once the data is in better shape that can be dealt with if needed.
Another point in regards to re-pivoting data.
This sample data...
...is interesting to me because it's all text and can be analyzed a few different ways: unpivoted and aggregated, converting it to an adjacency matrix (Showing relational data by Instrument or by Person, for example), unpivot/sort/create tree map/sunburst chart, etc.
That may be a topic for another discussion!