2

I struggle to implement the moving average formula in my function. Took me quite a while to get where the code is right now.

Is there a library I could probably take?

Input:

ma([2,3,4,3,2,6,9,3,2,1], 4)

Expected Output:

[None, None, None, 3.0, 3.0, 3.75, 5.0, 5.0, 5.0, 3.75]

My Output:

[None, None, 0.0, 3.0, 3.75, 5.0, 5.0, None, None, None]

I am running into the problem that the middle parts of my result is right but the rest is a mystery.

  1. Why does it return None for the last three values in the list?

def ma(prices, n):

ma = [] sums = [] s = 0 ave = 0

for idx, i in enumerate(prices):
    s += i
    sums.append(s)
    print('idx: ' + str(idx))
    print('list of sums ' + str(sums))
    #print('sum ' + str(s))

if i >= n+1:
    print('sums[idx] ' + str(sums[idx]))
    print('sums[idx-n] ' + str(sums[idx-n]))
    ave = (sums[idx] - sums[idx-n]) / n
    print('ave ' + str(ave))
    ma.append(ave)
    print('ma ' + str(ma))
else:
    m = None
    ma.append(m)
    print('ma ' + str(ma))

(Sorry for all those print function calls, but I really wanted to get to the source of the issue).

dobero
  • 71
  • 5
  • 1
    Can you use the standard library? If so, check out [`itertools`](https://docs.python.org/3/library/itertools.html) and [`statistics`](https://docs.python.org/3.5/library/statistics.html) modules. It can make your job easier. – bla Sep 16 '18 at 15:12

4 Answers4

2

There were several other logical errors in your code. I tried to correct it to make it work as you want. Following is only the modified version of the for loop. Rest stays the same. The added/modified lines are highlighted by a comment

for idx, i in enumerate(prices):
    s += i
    sums.append(s)
    if idx == n-1: # Added
        ave = (sums[idx]) / n  # Added 
        ma.append(ave)  # Added
    elif idx >= n: # modified
        ave = (sums[idx] - sums[idx-n]) / n
        ma.append(ave)
    else:
        ma.append(None) # removed extra variable m

The problem was that you were using the wrong variable as the index:

One major problem was that you were using

if i >= n+1:

You should use:

if idx >= n+1:

Moreover, I added an if statement to take care of the average of the first three elements.

Now

moving_average([2,3,4,5,8,5,4,3,2,1], 3)

gives the following output (you can round off later):

[None, None, 3.0, 4.0, 5.666666666666667, 6.0, 5.666666666666667, 4.0, 3.0, 2.0]
Sheldore
  • 37,862
  • 7
  • 57
  • 71
  • Thank you so much for that extensive explanation!!! I should work on how I define variables... Since also a major issue I was sitting on for an hour in there resulted of wrong usage... – dobero Sep 16 '18 at 15:15
2

If you are ok using the standard library, this might help. What you really need is a sliding window over your iterator. You can use this function for that (this was based on grouper from itertools recipes):

from itertools import islice

def window(iterable, n=2):
    # window('123', 2) --> '12' '23'
    args = [islice(iterable, i, None) for i in range(n)]
    return zip(*args)

For the average you can use statistics.mean. The paddig part can be simply achieved by adding the average list with [None] * (n - 1):

from statistics import mean

def moving_average(prices, n):
    avgs = [mean(w) for w in window(prices, n)]
    padding = [None] * (n - 1)

    return padding + avgs

Sample usage:

>>> moving_average([2,3,4,5,8,5,4,3,2,1], 3)
[None, None, 3, 4, 5.666666666666667, 6, 5.666666666666667, 4, 3, 2]
>>> moving_average([1, 2, 3], 3)
[None, None, 2]
>>> moving_average([1, 2, 3], 1)
[1, 2, 3]
>>> moving_average([5, 10, 0], 2)
[None, 7.5, 5]
bla
  • 1,840
  • 1
  • 13
  • 17
0

The reason why your program returned that 9-9 / 3 = 0 is negative indexing. When idx is 2, sums[idx-n] is saying sums[-1], which points at the last item of the list, 9. Understanding Python's slice notation could help explain that.

kaboom
  • 50
  • 9
  • definitely gonna have a look on that! Index and slicing is still under construction in my head – dobero Sep 16 '18 at 15:16
0

You could also solve this using list slicing to partition your input list smartly and calculate the avg over the list-partitions:

def moving_average(data,window):
    """The partitions begin with window-1 None. Then follow partial lists, containing
       window-sized elements. We do this only up to len(data)-window+1 as the following
       partitions would have less then window elements."""

    parts = [None]*(window-1) + [ data[i:i+window] for i in range(len(data)-window+1)]
    #       The None's           The sliding window of window elements

    # we return None if the value is None else we calc the avg
    return [ sum(x)/window if x else None for x in parts] 

print( moving_average([2,3,4,5,8,5,4,3,2,1], 1) )
print( moving_average([2,3,4,5,8,5,4,3,2,1], 2) )
print( moving_average([2,3,4,5,8,5,4,3,2,1], 3) )

Output (parts included as comment):

# [[2], [3], [4], [5], [8], [5], [4], [3], [2], [1]]
[2.0, 3.0, 4.0, 5.0, 8.0, 5.0, 4.0, 3.0, 2.0, 1.0]

# [None, [2, 3], [3, 4], [4, 5], [5, 8], [8, 5], [5, 4], [4, 3], [3, 2], [2, 1]]
[None, 2.5, 3.5, 4.5, 6.5, 6.5, 4.5, 3.5, 2.5, 1.5]

# [None, None, [2, 3, 4], [3, 4, 5], [4, 5, 8], [5, 8, 5], [8, 5, 4], 
#              [5, 4, 3], [4, 3, 2], [3, 2, 1]]
[None, None, 3.0, 4.0, 5.666666666666667, 6.0, 5.666666666666667, 4.0, 3.0, 2.0]
Patrick Artner
  • 50,409
  • 9
  • 43
  • 69