Search code examples
pythonlistmoving-averageindices

How to get the values in a Python list when their indices are mixed at the start and end


I was trying to perform a simple moving average of a list with the particularity that the end of the list it's the start of itself, but when, for example, with a list from 1 to 9 using 3 samples I try to:

original_list = [*range(1,10)]

print(sum(original_list[-1:2])/3) 

The ouput is 0.0 and should be (9+1+2)/3 = 4.

My first guess was that I wasnt chosing the right values from the indices, so I printed them individually:

print(original_list[-1:], original_list[:2])

# Print:
# [9] [1, 2]

Which was as expected, so I what's wrong in my previous print? How should I do it?

What I tried is to do both separed and join them so:

print(sum(original_list[-1:] + original_list[:2]) / 3)

# Print:
# 4

That is the right value, however if I increase the samples to 5 for example and I try:

import math
original_list = [*range(1,10)]

def MA(index):
    to_be_averaged = original_list[-2 + index:] + original_list[:3 + index]
    print(to_be_averaged)
    print(sum(to_be_averaged) / len(to_be_averaged))

MA(0)
# Print
# [8, 9, 1, 2, 3]
# 4.6

MA(1)
# Print
# [9, 1, 2, 3, 4]
# 3.8

MA(2)
# Print
# [1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5]
# 4.285714285714286

What a mess at index 2, what's going on? How should I do it? I feel like it should be a simple way to get a range from a to b independently if the indices are positive or negative.


Solution

  • Thanks for the answers and comments, it really helped me to undertand the issue!

    However, none of the addressed to the main question of how to do the moving average as it was presented. I ended up doing little tricks and a change of how to create the new list to be averaged (instead of slicing, list comprehension).

    import math
    import numpy as np
    
    def MA(index, sample):
        N = len(original_list)
        neighbors = math.floor(sample/2)
        desired_range = (np.array(range(sample)) - neighbors + index) % N
        
        to_be_averaged = [original_list[i] for i in desired_range]
        print(to_be_averaged)
        return sum(to_be_averaged)/sample
    

    I try to explain line by line:

    N = len(original_list)
    

    Gets the total lenght of the original list. In this case could be done only once but in my application I might use this function for different lists so I also input the list and this variable N changes.

    neighbors = math.floor(sample/2)
    

    This allows me to know how many up and down samples I'll be taking, remember that for this type of calculations tend to use odd numbers so your index is in the center. math.floor just rounds to the lowest integral if nothing else is passed.

    desired_range = (np.array(range(sample)) - neighbors + index) % N
    

    There is the funny part, here I try to get the range that I want but in indices to be used after. I create an np.array so I can perform addition and substraction operations to a list, which I create using range, the size of the range is the sample that I want and I adjust it to be previous and after samples just slicing it by substracting the neighbors, then I add the desired index so like that I have the indeces of the positions of interest, however it can happend that we add too much and get out of range of the list, that's why using the mod % N wraps around the list size and anything above N starts from the beginning of the list.

    The rest is basically a list comprehension along to the desired range which we already created and suming and dividing by the sample size.

    Hope it helps!

    original_list = [*range(5)]
    
    # Tests
    
    [print(MA(i,3)) for i in range((len(original_list)))]
    # [4, 0, 1]
    # 1.6666666666666667
    # [0, 1, 2]
    # 1.0
    # [1, 2, 3]
    # 2.0
    # [2, 3, 4]
    # 3.0
    # [3, 4, 0]
    # 2.3333333333333335
    [print(MA(i,5)) for i in range((len(original_list)))]
    # [3, 4, 0, 1, 2]
    # 2.0
    # [4, 0, 1, 2, 3]
    # 2.0
    # [0, 1, 2, 3, 4]
    # 2.0
    # [1, 2, 3, 4, 0]
    # 2.0
    # [2, 3, 4, 0, 1]
    # 2.0