Power Query - Allocate Amount (Reverse Running Total) -- simplification

Comfy

Well-known Member
Joined
Dec 21, 2009
Messages
3,386
Hi,

I have created a proof of concept that allows me to allocate expenditure to different budgets (funding streams) based on a priority.

But the solutions feels abit clunky partly due to the final IF statement.

Does anyone have any better ideas of how to accomplish this?

Query - Source

Power Query:
let
    //Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
    Source = Table.FromRows(Json.Document(Binary.Decompress(Binary.FromText("lZIxCsAgDEXv4uwQ00rp2muIW5dC196/xhrIUv0Gg394wkN+Si7wskbn3fFc91nuICvTMpWNRC77PssT7DbB1khUX/yxLAP6Kov4Kov4KlvOPtBdZEBdZRFdZRFdZSV2dHmiDZYd6Vp2pGtZib3v5Yk2WBbxRdtg2RoHvmgdLIv4onWw7FeH5ptf", BinaryEncoding.Base64), Compression.Deflate)), let _t = ((type nullable text) meta [Serialized.Text = true]) in type table [Account = _t, Type = _t, Period = _t, Project = _t, Fund = _t, Amount = _t, Budget = _t]),
    #"Changed Type" = Table.TransformColumnTypes(Source,{{"Account", Int64.Type}, {"Type", type text}, {"Project", type text}, {"Fund", Int64.Type}, {"Amount", Currency.Type}, {"Budget", Currency.Type}, {"Period", type text}}),
    #"Unpivoted Only Selected Columns" = Table.Unpivot(#"Changed Type", {"Amount", "Budget"}, "Attribute", "Value"),
    #"Filtered Rows" = Table.SelectRows(#"Unpivoted Only Selected Columns", each ([Value] <> 0)),
    #"Added Custom" = Table.AddColumn(#"Filtered Rows", "Priority", each if [Fund] = null and [Attribute] = "Amount" then 0 else if [Fund] = null then 99 else [Fund]),
    #"Grouped Rows" = Table.Group(#"Added Custom", {"Period", "Project"}, {{"Data", each Table.Sort(_,{{"Period", Order.Ascending}, {"Project", Order.Ascending}, {"Priority", Order.Ascending}}), type table}}),
    #"Added Custom2" = Table.AddColumn(#"Grouped Rows", "WithTotal", each CreateTable([Data])),
    #"Removed Other Columns" = Table.SelectColumns(#"Added Custom2",{"WithTotal"}),
    #"Expanded WithTotal" = Table.ExpandTableColumn(#"Removed Other Columns", "WithTotal", {"Account", "Type", "Period", "Project", "Fund", "Attribute", "Value", "Amount", "Priority", "RunningTotal"}, {"Account", "Type", "Period", "Project", "Fund", "Attribute", "Value", "Amount", "Priority", "RunningTotal"}),
    #"Filtered Rows1" = Table.SelectRows(#"Expanded WithTotal", each ([Attribute] = "Budget")),
    #"Added Custom3" = Table.AddColumn(#"Filtered Rows1", "Actual", each if [Priority] = 7 then if [RunningTotal] > 0 then [RunningTotal] else 0 else if [RunningTotal] > [Value] then [Value] else if [RunningTotal] > 0 then [RunningTotal] else 0),
    #"Removed Columns" = Table.RemoveColumns(#"Added Custom3",{"Attribute", "Amount", "Priority", "RunningTotal"}),
    #"Renamed Columns" = Table.RenameColumns(#"Removed Columns",{{"Value", "Budget"}, {"Actual", "Amount"}})
in
    #"Renamed Columns"

Query - fxGroupedRunningTotal
Power Query:
(values as list, grouping as list) as list =>

let
    GRTList = List.Generate
    (
        ()=> [ GRT = values{0}, i = 0 ],

        each [i] < List.Count(values),

        each try if [i] = 0
            then [GRT = [GRT], i = [i] + 1]
            else [GRT = [GRT] - values{[i] + 1}, i = [i] + 1]
        otherwise [i = [i] + 1],

    each [GRT])
in
    GRTList

Query - CreateTable
Power Query:
(Source as table)=>

let

    List1 = Source[Period],
    List2 = Source[Project],
    Zipped = List.Zip({List1,List2}),
    ToTable = Table.FromList(Zipped, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
    Combined = Table.TransformColumns(ToTable, {{"Column1", each Text.Combine(_), type text}}),
   
    BufferedValues = List.Buffer(Source[Value]),
    BufferedGrouping = List.Buffer(Combined[Column1]),

    RT = Table.FromColumns(
        {
            Source[Account],
            Source[Type],
            Source[Period],
            Source[Project],        
            Source[Fund],
            Source[Attribute],
            Source[Value],
            Source[Priority],
            fxGroupedRunningTotal(BufferedValues, BufferedGrouping)
        },
        {
            "Account",
            "Type",
            "Period",
            "Project",        
            "Fund",
            "Attribute",
            "Value",
            "Priority",
            "RunningTotal"
        })


in
    RT
 

Excel Facts

Select all contiguous cells
Pressing Ctrl+* (asterisk) will select the "current region" - all contiguous cells in all directions.
Hi,

Just looked at the if part you mentioned, and I think it can shortened like this
Power Query:
if [Priority] <> 7 then 0
else if RunningTotal] > 0 then [RunningTotal]
else if [RunningTotal] > [Value]  then [Value] 
else 0

PS: I do not not need running totals much, but I've seen shorter codes not requiring a custom function using list.generate (which I'm unable to master myself, and so I don't fully understand what yours does exactly.)
 
Upvote 0
Thanks GraH,

I'm not sure your restructure of the IF works but you have certainly helped to point me in the right direction as I can now see the duplication. Thanks!

And so I have revised the IF to the following:

Power Query:
if [RunningTotal] > [Value] and [Priority] <> 7 then [Value]
else if [RunningTotal] > 0 then [RunningTotal]
else 0

I've repurposed other code written to generate a running total and converted it so that rather than starting from zero and incrementing upwards. It starts at a ceiling and is reduced for the total being counted.

Using the #"Expand WithTotal" step from the source Query:
Book1
ABCDEFGHIJ
1AccountTypePeriodProjectFundAttributeValueAmountPriorityRunningTotal
212345Build111111Amount110001100
312345Build1111111Budget50011100
412345Build1111112Budget5002600
512345Build1111117Budget5007100
612345Build122222Amount9000900
712345Build1222221Budget5001900
812345Build1222222Budget5002400
912345Build1222227Budget5007-100
1012345Build133333Amount3000300
1112345Build1333331Budget5001300
1212345Build1333332Budget5002-200
1312345Build1333337Budget5007-700
1412345Build211111Amount190001900
1512345Build2111111Budget50011900
1612345Build2111112Budget50021400
1712345Build2111117Budget5007900
1812345Build222222Amount220002200
1912345Build2222221Budget50012200
2012345Build2222222Budget50021700
2112345Build2222227Budget50071200
2212345Build233333Amount300003000
2312345Build2333331Budget50013000
2412345Build2333332Budget50022500
2512345Build2333337Budget50072000
Sheet4


For project 11111 in period 1 I want to allocate the 1100 "Amount" across the 3 different funds up to the maximum of the "Budget" value.
To do this I have used the list.generate to add the reducing running total to each row, this is the amount available for allocation (I repurposed code that was written for a normal running total)
I then use the if statement to say that if the remaining amount for allocation is greater than the budget the use the budget total.
The caveat being that Fund 7 budget can be exceeded. This is to ensure that the total allocation is allocated in full i.e. the Total of column [Value] by [Attribute] "Amount" equals the total of column [Value] by [Attribute] "Budget" (Green equals orange in the following)

Book1
ABCDEFGHIJK
1AccountTypePeriodProjectFundAttributeValueAmountPriorityRunningTotalActual
212345Build111111Amount1100011001100
312345Build1111111Budget50011100500
412345Build1111112Budget5002600500
512345Build1111117Budget5007100100
Sheet4


The Fund 7 caveat is best shown by looking at project 33333 in period 2.
Book1
ABCDEFGHIJK
1AccountTypePeriodProjectFundAttributeValueAmountPriorityRunningTotalActual
2212345Build233333Amount3000030003000
2312345Build2333331Budget50013000500
2412345Build2333332Budget50022500500
2512345Build2333337Budget500720002000
Sheet4



The proof of concept I have works with a small dataset but I'd like to try and clean it up/re-write in a different way so that it performs better using a real world data set.
 
Last edited:
Upvote 0
Thanks for the feedback. Glad I could push a tiny little bit :-)
I understand better now where you're adding. Can't help much more here I'm afraid.
For sure others will jump in and provide stunning M.
 
Upvote 0
Not sure about this.
Maybe try


Power Query:
let
    Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
    Grouped = Table.Group(Source, {"Account", "Type", "Period", "Project"}, {{"A", each Table.AddColumn(_, "Actual", (x)=>  List.Max({ if x[Priority]=List.Max([Priority]) then x[RunningTotal] else 0  ,List.Min({x[Value],x[RunningTotal]}),0 }) )}}),
    Combine = Table.Combine(Grouped[A])
in
    Combine


PQ Reverse Running Total.xlsx
ABCDEFGHIJK
1AccountTypePeriodProjectFundAttributeValueAmountPriorityRunningTotalActual
212345Build111111Amount1100011001100
312345Build1111111Budget50011100500
412345Build1111112Budget5002600500
512345Build1111117Budget5007100100
612345Build122222Amount9000900900
712345Build1222221Budget5001900500
812345Build1222222Budget5002400400
912345Build1222227Budget5007-1000
1012345Build133333Amount3000300300
1112345Build1333331Budget5001300300
1212345Build1333332Budget5002-2000
1312345Build1333337Budget5007-7000
1412345Build211111Amount1900019001900
1512345Build2111111Budget50011900500
1612345Build2111112Budget50021400500
1712345Build2111117Budget5007900900
1812345Build222222Amount2200022002200
1912345Build2222221Budget50012200500
2012345Build2222222Budget50021700500
2112345Build2222227Budget500712001200
2212345Build233333Amount3000030003000
2312345Build2333331Budget50013000500
2412345Build2333332Budget50022500500
2512345Build2333337Budget500720002000
RevRuningTotal
 
Upvote 0
Thanks Bo_Ry,

I really like that and will replace the IF statement. I can never remember how to pass the group to itself as a variable for the generator function so I'll have to put this in my reference file for later :)

Latest M for Query "Source"
Power Query:
let
    //Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
    Source = Table.FromRows(Json.Document(Binary.Decompress(Binary.FromText("lZIxCsAgDEXv4lxKTCvFtdcQty6Frr1/TWsgS/UbDP7hCQ/5KTnPyxrc5Pb7vI5ye1mZmqlsIHJ5arM8wG4DrEQONMf4PvrDWQZUVhZRVhZRVracSNTUXWRAXWURXWURXWUlNnR5oBCW7elatqdrWYmt7+WBNlgW8UXbYNk3dnzROlgW8UXrYNmvDtU3Pw==", BinaryEncoding.Base64), Compression.Deflate)), let _t = ((type nullable text) meta [Serialized.Text = true]) in type table [Account = _t, Type = _t, Period = _t, Project = _t, Fund = _t, Amount = _t, Budget = _t]),
    #"Changed Type" = Table.TransformColumnTypes(Source,{{"Account", Int64.Type}, {"Type", type text}, {"Project", type text}, {"Fund", Int64.Type}, {"Amount", Currency.Type}, {"Budget", Currency.Type}, {"Period", type text}}),
    #"Unpivoted Only Selected Columns" = Table.Unpivot(#"Changed Type", {"Amount", "Budget"}, "Attribute", "Value"),
    #"Filtered Rows" = Table.SelectRows(#"Unpivoted Only Selected Columns", each ([Value] <> 0)),
    #"Added Custom" = Table.AddColumn(#"Filtered Rows", "Priority", each if [Fund] = null and [Attribute] = "Amount" then 0 else if [Fund] = null then 99 else [Fund]),
    #"Grouped Rows" = Table.Group(#"Added Custom", {"Period", "Project"}, {{"Data", each Table.Sort(_,{{"Period", Order.Ascending}, {"Project", Order.Ascending}, {"Priority", Order.Ascending}}), type table}}),
    #"Added Custom2" = Table.AddColumn(#"Grouped Rows", "WithTotal", each CreateTable([Data])),
    #"Removed Other Columns" = Table.SelectColumns(#"Added Custom2",{"WithTotal"}),
    #"Expanded WithTotal" = Table.ExpandTableColumn(#"Removed Other Columns", "WithTotal", {"Account", "Type", "Period", "Project", "Fund", "Attribute", "Value", "Amount", "Priority", "RunningTotal"}, {"Account", "Type", "Period", "Project", "Fund", "Attribute", "Value", "Amount", "Priority", "RunningTotal"}),
    #"Filtered Rows1" = Table.SelectRows(#"Expanded WithTotal", each ([Attribute] = "Budget")),
    Grouped = Table.Group(#"Filtered Rows1", {"Account", "Type", "Period", "Project"}, {{"A", each Table.AddColumn(_, "Actual", (x)=>  List.Max({ if x[Priority]=List.Max([Priority]) then x[RunningTotal] else 0  ,List.Min({x[Value],x[RunningTotal]}),0 }) )}}),
    Combine = Table.Combine(Grouped[A]),
    #"Removed Columns" = Table.RemoveColumns(Combine,{"Attribute", "Amount", "Priority", "RunningTotal"}),
    #"Renamed Columns" = Table.RenameColumns(#"Removed Columns",{{"Value", "Budget"}, {"Actual", "Amount"}})
in
    #"Renamed Columns"
 
Upvote 0
Hi Bo_Ry or Excel Wizard, you're here too? :cool: You do like to use
Power Query:
(x)=>
type constructions. Somewhat a slippery slope for me...
 
Upvote 0
The problem I have is that I don't understand how M establishes what the x object contains.

I guess I have some reading to do.
 
Upvote 0
Hi Grah. :)

(x) = > x is called Custom function and this is the full form of each

This is a must when you need to refer to something on other tables or records.

Please check Mike's video with a great explanation.
 
Upvote 0
EDIT: I pressed enter, and at the same time Bo_Ry posted his reaction.

@Comfy,
how I understand it... (I also have it hard to completely get it though)
x is the table from the Table.Group, so the sub element. (x) => is much like a custom function construction. What's after the "=>" is then applied on each sub table.
It is a condensed way of doing transformations on a Group By All Rows via the UI.

@Bo_Ry , would this be close?
 
Upvote 0

Forum statistics

Threads
1,223,911
Messages
6,175,325
Members
452,635
Latest member
laura12345

We've detected that you are using an adblocker.

We have a great community of people providing Excel help here, but the hosting costs are enormous. You can help keep this site running by allowing ads on MrExcel.com.
Allow Ads at MrExcel

Which adblocker are you using?

Disable AdBlock

Follow these easy steps to disable AdBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the icon in the browser’s toolbar.
2)Click on the "Pause on this site" option.
Go back

Disable AdBlock Plus

Follow these easy steps to disable AdBlock Plus

1)Click on the icon in the browser’s toolbar.
2)Click on the toggle to disable it for "mrexcel.com".
Go back

Disable uBlock Origin

Follow these easy steps to disable uBlock Origin

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back

Disable uBlock

Follow these easy steps to disable uBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back
Back
Top