Home > front end >  Subtract a value from each value in a column without any value going below 0
Subtract a value from each value in a column without any value going below 0

Time:03-19

I'm stuck on an issue in PowerQuery involving rounding monthly values but trying to keep the difference in the yearly totals less than 1.

I have data in this format:

Year Month Value Value Rounded Difference
2020 1 0.3 0 0.3
2020 2 1.6 2 -0.4
2020 3 3.5 4 -0.5
2020 4 2.5 3 -0.5

As above the yearly total would be 7.9 but with rounding it's 9.

What I want to do is take the total difference each year and use it to adjust the rounded values up or down so that the yearly difference rounds to 0. I also want to ensure the the final rounded values don't drop below 0.

Using the example above the output data would look like this:

Year Month Value Value Rounded Difference Final Value
2020 1 0.3 0 0.3 0
2020 2 1.6 2 -0.4 1
2020 3 3.5 4 -0.5 4
2020 4 2.5 3 -0.5 3

Now the difference between the yearly total and the final value total is only 0.1.

I figure I want to write a function that works out the total difference in the year and uses that as a counter to iterate against as it adjusts each row, but I'm not sure how to do that.

Also I realise that maybe a better approach would avoid this issue altogether! Thanks!

CodePudding user response:

"I want to do something with a number but I don't want the number to be negative."

There's a general approach for this kind of question:

=MAX(do_something(number), 0)

Like this, when the result of your operation yields a negative number, it will automatically be replaced by zero.

CodePudding user response:

I don't know how you are generating your Value Rounded column, but the algorithm you are using seems to be round 1/2 away from zero.

The M Number.Round function will round 1/2 to even which reduces the bias inherent in an algorithm that always rounds in the same direction.

Using that, the difference between the sums will only be 0.1.

If that is not good enough, there are other rounding algorithms that you could use. The choice of a rounding algorithm will also depend on the distribution of values. And that is something that cannot be determined by your example. It is certainly possible to construct a set of values for which the differences are large (eg: 0.5 for all values using a round 1/2 to even algorithm), but that would probably be uncommon in real life.

For example:

Original Table
enter image description here

let
    Source = Excel.CurrentWorkbook(){[Name="Table9"]}[Content],
    #"Changed Type" = Table.TransformColumnTypes(Source,{{"Year", Int64.Type}, {"Month", Int64.Type}, {"Value", type number}}),
    #"Added Custom" = Table.AddColumn(#"Changed Type", "Rounded Values", each Number.Round([Value])),

//add totals row
    totals = Table.Combine({#"Added Custom",
     Table.FromRecords({
         [  Year="Totals", 
            Month=null, 
            Value = List.Sum(#"Added Custom"[Value]), 
            Rounded Values = List.Sum(#"Added Custom"[Rounded Values])]})})
     
in
    totals

Result
enter image description here

Edit: Here is code that does what you describe as well as round-to-even. For certain values, your adjustment method seems to work better. I have included both the Round to even and Adjust rounding columns in the results, and also added a Totals row, to make it easier for you to compare the results

Source
enter image description here

M Code

let
    Source = Excel.CurrentWorkbook(){[Name="Table9"]}[Content],
    #"Changed Type" = Table.TransformColumnTypes(Source,{{"Year", Int64.Type}, {"Month", Int64.Type}, {"Value", type number}}),

//round 1/2 to even
    roundToEven = Table.AddColumn(#"Changed Type", "Rounded Values", each  Number.Round([Value])),

//Per your algorithm
    roundAdjust = let 
        nums = roundToEven[Rounded Values],
        adjustmentTotal = Number.Round(List.Sum(#"Changed Type"[Value]) - List.Sum(nums)),
        ns = Number.Sign(adjustmentTotal),
        adjust = List.Generate(
            ()=>[n=List.Max({0,nums{0} Number.Sign(adjustmentTotal)}), adj=adjustmentTotal-Number.Sign(adjustmentTotal), idx=0],
            each [idx] < List.Count(nums),
            each [n=if [adj]=0 then nums{[idx] 1} else nums{[idx] 1} Number.Sign(adjustmentTotal), 
                adj=if [adj]=0 then 0 else [adj]-Number.Sign(adjustmentTotal), idx=[idx] 1],
            each [n])
        in adjust,
//add as a column
    addCol = Table.FromColumns(
        Table.ToColumns(roundToEven) & {roundAdjust},
        type table[Year=Int64.Type, Month=Int64.Type, Value=number, Rounded Values=Int64.Type, Round Adjusted=Int64.Type]
    ),

//add totals row
    totals = Table.Combine({addCol,
     Table.FromRecords({
         [  Year=null, 
            Month="TOTAL:", 
            Value = List.Sum(addCol[Value]), 
            Rounded Values = List.Sum(addCol[Rounded Values]),
            Round Adjusted = List.Sum(addCol[Round Adjusted])
            ]})})
in
    totals

Results
enter image description here

  • Related