Forum Discussion
Fill BLANKs down, up or both in column(s) - similar to Power Query
- Nov 27, 2023
My variant:
Filler =LAMBDA(table,LET( header, TAKE(table, 1), data, DROP(table, 1), counter, SEQUENCE(COLUMNS(data)), FillDown, LAMBDA(a, v, IF(v = "", a, v)), Complete, LAMBDA(vector, LET(ini, TAKE(TOCOL(vector, 1), 1), SCAN(ini, vector, FillDown))), Stack, DROP(REDUCE("", counter, LAMBDA(a, v, HSTACK(a, Complete(INDEX(data, , v))))), , 1), VSTACK(header, Stack) ))
I like this formula. Sure that could be other variants, as for everything in Excel (and not only), but the only reason to revise the formula could be the performance, if it works fine on hundreds thousands of rows. On the other hand such is not practical for the majority of Excel cases.
It works fine and do it function, thank you for sharing.
I'm usually concerned by perf. (and scalabily) and did check with a few thousands of rows, no problem from my perspective although there are probably faster approaches. TBH I'm not too concerned about this for this kind of transformation as, theoritically, this isn't somethig people have to do ten times a day
Didn't post it but also checked with 'large' tables. To give you a sense (on Core i7/8 GB/SSD):
Random table 300k rows * 7 cols to fill down then up
- LAMBDA 12 secs
- PQ (firewall off) 6 secs
Same random table 500k rows * 7 cols to fill down then up
- LAMBDA 22 secs
- PQ (firewall off) 9 secs
- SergeiBaklanNov 29, 2023Diamond Contributor
Interesting, thank you. Yes, good enough for most practical cases.
- LorenzoNov 29, 2023Silver Contributor
And timing obviously varies between tables. I took a different random one still 300k * 7 cols to fill Down then Up:
- PQ (firewall off): 6sec
- FillColumns I posted earlier: 13+sec
Also compared, with same table, 3 options:
// By @Patrick2788: 5sec =LAMBDA(table, LET( header, TAKE(table, 1), data, DROP(table, 1), counter, SEQUENCE(COLUMNS(data)), FillDown, LAMBDA(a, v, IF(v = "", a, v)), Complete, LAMBDA(vector, LET(ini, TAKE(TOCOL(vector, 1), 1), SCAN(ini, vector, FillDown))), Stack, DROP(REDUCE("",counter, LAMBDA(a,v, HSTACK(a,Complete(INDEX(data,,v))))),,1), VSTACK(header, Stack) ) )
// From thread 'Can SCAN() and BYROW() be combined?': 6sec =LAMBDA(array, LET( fnFill, LAMBDA(array, LET( reverse, LAMBDA(arr, SORTBY(arr, SEQUENCE(ROWS(arr)), -1)), down, LAMBDA(arr, SCAN(, arr, LAMBDA(a, v, IF(v = "", a, v)))), reverse(down(reverse(down(array)))) ) ), fillData, LAMBDA(arr, REDUCE( 1, SEQUENCE(COLUMNS(arr)), LAMBDA(A, i, IF(SEQUENCE(,i) = i, fnFill(INDEX(arr,, i )), A)) ) ), VSTACK(TAKE(array,1), fillData(DROP(array,1)) ) ) )
// From FillColums as a separate LAMBDA: 5sec // with @Patrick2788 packaging =LAMBDA(table, LET( header, TAKE(table, 1), data, DROP(table, 1), counter, SEQUENCE(COLUMNS(data)), FirstValue, LAMBDA(arr, IF(TAKE(arr,1)="",XLOOKUP(TRUE,arr<>"",arr,""),"")), Fill, LAMBDA(seed,v, IF(v="",seed,v)), FillCol, LAMBDA(arr, SCAN(FirstValue(arr),arr,Fill)), Stack, DROP(REDUCE("",counter, LAMBDA(a,v, HSTACK(a,FillCol(INDEX(data,,v))))),,1), VSTACK(header, Stack) ) )
- djclementsNov 29, 2023Bronze Contributor
Lorenzo Very nice display of various dynamic array techniques here. Personally, I still prefer VBA for filling down. It requires very little code, is highly efficient, and can be easily adapted to fit any scenario (ie: excluding certain rows or filling down based on values in another column). The basic fill down code for your original screenshot of Table2 would be something like this:
Sub FillDown() Dim rg As Range, arr As Variant, i As Long, j As Long Set rg = Sheet1.ListObjects("Table2").DataBodyRange arr = rg.Value For i = 2 To UBound(arr, 1) For j = 1 To UBound(arr, 2) If arr(i, j) = vbNullString Then arr(i, j) = arr(i - 1, j) Next j Next i rg.Value = arr End Sub
I've never ran into a scenario where I also needed to fill up; however, that can be done by simply reversing the direction of the first loop and filling values from the row below:
Sub FillDownUp() Dim rg As Range, arr As Variant, i As Long, j As Long Set rg = Sheet1.ListObjects("Table2").DataBodyRange arr = rg.Value 'Fill down For i = 2 To UBound(arr, 1) For j = 1 To UBound(arr, 2) If arr(i, j) = vbNullString Then arr(i, j) = arr(i - 1, j) Next j Next i 'Fill up For i = UBound(arr, 1) - 1 To 1 Step -1 For j = 1 To UBound(arr, 2) If arr(i, j) = vbNullString Then arr(i, j) = arr(i + 1, j) Next j Next i rg.Value = arr End Sub
When tested with 520,000 rows x 5 columns of data, it completed in 4.25 seconds; with 1,040,000 rows of data, it took 8.5 seconds.