Search code examples
pythonlistfloating-pointnormalize

How to normalize a list of floats when one value has to stay the same?


I have a list with these normalized values

list_a = [0.25, 0.25, 0.25, 0.25]

Now I want to change the value of one entry to another number, let's say 0.75. This number is fixed and shouldn't change anymore.

list_a_changed = [0.25, 0.25, 0.75, 0.25]

To still make sure the sum of all the values in the list add up to 1, the remaining values need to be 0.0833. So my list will have to be:

list_a_normalized = [0.083, 0.083, 0.75, 0.083]

This is easy enough to figure out if all values share the same percentage in the initial list. I can just do 1 - 0.75 = 0.25, and divide that 0.25 between remaining numbers since they all hold the same percentage of the total sum.

value_change = 0.75
remaining_value = 1 - value_change
divided_remaining_value = remaining_value / (len(list_a_changed) - 1)

list_a_normalized = [divided_remaining_value, divided_remaining_value, value_change, divided_remaining_value ]


But how would you do it if the original list was something like:

list_b = [0.25, 0.45, 0.20, 0.10]

And I change one value to 0.05

list_b_changed = [0.25, 0.45, 0.05, 0.10]

How would you calculate what the values of the other numbers will have to be so they each hold the appropriate portion of the remaining 0.95?


Solution

  • You may

    • compute the remaining
    • compute the total without the changed value, to get their relative proportion without the changed value
    • multiply their value to the remaining to get it into account, and divide by the relative total to get them proportionnate to the total
    def normalize(values, index_not_change):
        remaining = 1 - values[index_not_change]
        total_except_remaining = sum(values) - values[index_not_change]
        return [(value * remaining / total_except_remaining if idx != index_not_change else value)
                for idx, value in enumerate(values)]
    
    print(normalize([0.25, 0.25, 0.75, 0.25], 2)) # [0.0833333333, 0.0833333333, 0.75, 0.0833333333]
    print(normalize([0.25, 0.45, 0.05, 0.10], 2)) # [0.296875, 0.534375, 0.05, 0.11875000000000001]
    

    To understand the total_except_remaining purpose, without it it'd have been that

    normalize([0.25, 0.25, 0.75, 0.25], 2) -> [0.0625, 0.0625, 0.75, 0.0625]
    

    because you'd have compute a quarter of the remaining (0.25) but adding the fact that the relative sum was 0.75 and not 1, you update to their real proportion


    You can put the modification in the same method too

    def normalize(values, position, new_value):
        values[position] = new_value
        remaining = 1 - new_value
        total_except_remaining = sum(values) - new_value
        return [(value * remaining / total_except_remaining if idx != position else value)
                for idx, value in enumerate(values)]
    
    print(normalize([0.25, 0.25, 0.25, 0.25], 2, 0.75))