Home > database >  Comparing values between two rows in the same 2D list, returning their percentage delta - Python
Comparing values between two rows in the same 2D list, returning their percentage delta - Python

Time:03-22

For this assignment, I cannot import any modules. I have this list

stateList = [['Set', 'state', 2022, 2023, 2024], ['A', 'start', 100, 100, 100],['A', 'end', 110, 100, 90]]

I want to append calculations made on this list to another list, which would look like this. The change in this new list would be in %.

stateChange = [['Set', 'state', 2022, 2023, 2024], ['A', 'change', 10, 0, -10]]

So far I have this code, which is not working

stateChange = []

for row in stateList[0:1]:
    stateChange.append(row)

for row in stateList [1:]:
    stateChange.append([])
    for column in row[2:]:
        chg = (((stateList [1][2]) - column)/column)*100
        stateChange[1].append(chg)     

print(stateChange)

It is giving me

[['Set', 'state', 2022, 2023, 2024], [0.0, 0.0, 0.0, -9.090909090909092, 0.0, 11.11111111111111], []]

How do I get around this? In essence, I am trying to take the value from the same column, but on the second row, and see what its percentage change is from that first row, and then append this to the new list.

Instead, it is showing a drop in 2022, and a climb in 2024, which is the inverse. And I am getting three extra 0.0's in the beginning of that appended second row in the new list.

CodePudding user response:

I'm not sure what the significance of the first element of your list is, and I assume there can be more sets than just "A", or maybe even possibly more entries than just "start" and "end", but only one start and end. I also assume that the list will always contain a start and an end for each state (so there are no orphaned states). If any of these conditions are not true, you'll have to implement checks and throw an exception or some other kind of error handling.

So the solution would be to first build a dictionary of starts and ends for each state and then just simply iterate through each state and calculate the necessary values.

def get_changes(state_list):
    starts = {}
    ends = {}

    # first build a dictionary of start and end states for each state (A, B, ...)
    for state in stateList:
        if state[1] == 'start':
            starts[state[0]] = state
        elif state[1] == 'end':
            ends[state[0]] = state

    state_changes = []        
    # iterate through each state name
    for state_name in starts.keys():
        # calculate column changes as (end-start)/start*100
        # first two columns are ignored
        column_changes = [
            (ends[state_name][i] - v)/v*100 
            for i, v in enumerate(starts[state_name]) 
            if i > 1
        ]
        # build the state change list
        state_changes.append([state_name, 'change', *column_changes])

    return state_changes

The function will only return the actual ["A", "change", 10, 0, -10] as I said I don't know the significance of that first value. I'm sure you can figure out how to glue them together.

Here's a live demo of the code running

CodePudding user response:

Let's add one more set "B". Since "Set" elements are always consecutive and in pairs, we could convert stateList[1:] to an iterator and use zip to traverse pairs together in a double-loop:

stateList = [['Set', 'state', 2022, 2023, 2024], 
             ['A', 'start', 100, 100, 100],['A', 'end', 110, 100, 90],
             ['B', 'start', 100, 100, 100],['B', 'end', 110, 100, 90]]

out = [stateList[0]]
it = iter(stateList[1:])
for lst1, lst2 in zip(it, it):
    tmp = [lst[0], 'change']
    tmp.extend([(y-x)/x*100 for x, y in zip(lst1[2:], lst2[2:])])
    out.append(tmp)

Output:

[['Set', 'state', 2022, 2023, 2024],
 ['A', 'change', 10.0, 0.0, -10.0],
 ['B', 'change', 10.0, 0.0, -10.0]]
  • Related