1

I'm developing a python script on a raspberry pi with a distance sensor. My code is working and i can reach the distance but I want to avoid some false positive/negative so I would like to get the average of the last three data.

Here is my code:

while True:
        distance= misura() # GETTING THE REAL DISTANCE IN CM
        print "Distanza : %.2f" % distanza

        avg_distance = <something> # <- HERE I NEED SOMETHING ELEGANT

        if (avg_distance > 30):
            print "NOT PRESENT"
        else:
            print "PRESENT"

        time.sleep(1.5)

I would like some function (maybe based on a list?) that returns the average of the last three (or n) distances

IMPORTANT: I don't want to store ALL the values because this script will run for days and days

Rolf of Saxony
  • 21,661
  • 5
  • 39
  • 60
Jack
  • 489
  • 1
  • 6
  • 18
  • in the loop get hold of the sum of all distances and the count, divide both to get the average – MEdwin May 28 '19 at 09:31
  • @MEdwin well, that's is how to calculate an average. I know that, but i want to store in a list just the last three values, without saving var1, var2, var3 and looping with an index. – Jack May 28 '19 at 09:35
  • Save the values in list and get last 3 values and sum it `list[-3:]` – Siva Shanmugam May 28 '19 at 09:37
  • @SivaShanmugam that's is what i thoug, but this script will run for days, and i dont want to store ALL the data – Jack May 28 '19 at 09:39
  • Use a `collections.deque` with a max length to only keep the last three. – Holloway May 28 '19 at 09:40
  • https://stackoverflow.com/questions/16415254/python-list-with-fixed-number-of-elements @Jack Check this. – Siva Shanmugam May 28 '19 at 09:43

9 Answers9

3

I would use a collections.deque.

from collections import deque
SAMPLE = 3
data = deque(maxlen=SAMPLE)
while True:
    distance = misura() # GETTING THE REAL DISTANCE IN CM
    print "Distanza : %.2f" % distanza

    data.append(distance)
    avg_distance = sum(data)/SAMPLE # <- HERE I NEED SOMETHING ELEGANT

    ... # rest as before

The deque is fixed length so you're only sampling the latest three. If you want to change the sampling period you only have to change one variable.

If you want to run this in a tight loop (ie, removing the 1.5s sleep), you could hold the sum in a variable to remove the O(n) operation each loop (even if it's not really an issue for only 3 elements). Something like:

total -= data.popleft()
total += distance

data.append(distance)
average = total / 3 
Holloway
  • 6,412
  • 1
  • 26
  • 33
2

If it is your objective to only remember the last three measurements and find their average, consider using a collections.deque object so that when you add to this object, it will only remember the last three elements that were added to it. You can find the average of the contents in the deque at each iteration:

from collections import deque
l = deque(maxlen=3) # New
while True:
    distance= misura() # GETTING THE REAL DISTANCE IN CM
    print "Distanza : %.2f" % distanza
    l.append(distance) # New
    #avg_distance = <something> # <- HERE I NEED SOMETHING ELEGANT
    avg_distance = sum(l) / len(l) # Compute average of the last three eleemnts

    if (avg_distance > 30):
        print "NOT PRESENT"
    else:
        print "PRESENT"

    time.sleep(1.5)

The nice thing about this is that when the deque is full and you append an item to it, it will remove the oldest added element in the deque and add in the newest element you're trying to add.

rayryeng
  • 102,964
  • 22
  • 184
  • 193
0

Well maybe you need just create a list like list = [misura(),misura(),misura()] to get three values respectively and pass it to a function like

def mean(list) :
    Total = 0
    For item in list :
        Total += item
    Return total /len(list)
0

You could take advantage of the fact that mutable default arguments are only created once, to store the last three values measured:

def last_three_average(val, last_three=[]):
    last_three.append(val)
    last_three = last_three[-3:]
    return sum(last_three) / len(last_three)

for idx in range(1, 10, 1):
    print(f'{idx}: {last_three_average(idx)}')

output:

1: 1.0
2: 1.5
3: 2.0
4: 3.0
5: 4.0
6: 5.0
7: 6.0
8: 7.0
9: 8.0

Or, using a deque i/o a list:

from collections import deque

def last_three_average(val, last_three=deque(maxlen=3)):
    last_three.append(val)
    return sum(last_three) / len(last_three)
Reblochon Masque
  • 35,405
  • 10
  • 55
  • 80
0

You can apply some math and recalculate average each new iteration.

passed = 0
avg_distance = 0
while True:
    passed += 1
    distance = misura()
    print("Distanza : %.2f" % distanza)

    avg_distance = ((passed - 1) * avg_distance + distance) / passed

    if (avg_distance > 30):
        print("NOT PRESENT")
    else:
        print("PRESENT")

    time.sleep(1.5)
Olvin Roght
  • 7,677
  • 2
  • 16
  • 35
  • 1
    Won't this keep an average of all the values from the start of the loop, not the final three values? – Holloway May 28 '19 at 09:51
  • This code will find the average of all distance values, not just the last three. You'll need to update the logic so that you're also keeping track of the average values for everything but the last three and subtract that from the total as you are looping. – rayryeng May 28 '19 at 09:53
  • @Holloway, exactly. I've just offered a way how to recalculate average without storing previous results. – Olvin Roght May 28 '19 at 10:03
0

If type(distance) is a list then you can simply calculate the average for last 3 points by

distance = distance[-3:] # This will remove the extra data and get only last 3 values

average = sum(distance) / len(distance)
Ibtihaj Tahir
  • 636
  • 5
  • 17
0

First create a list (outside the loop):

distances = [misura(),misura(),misura()]

Then your loop should be like this:

while True:
    distances.append(misura())
    print "Distanza : %.2f" % distances[-1]

    distances = distances[1:] # this way you keep only the last 3 values
    avg_distance = sum(distances) / len(distances)

    if (avg_distance > 30):
        print "NOT PRESENT"
    else:
        print "PRESENT"

    time.sleep(1.5)
ygeyzel
  • 11
  • 5
0

See another version with the logic of just saving the distance with only 3 elements in a loop. The last value is 'pop' out so we only have 3 elements.

my mockup below;

distancedata=[0,0,0]
for i in range(20):
        if len(distancedata) >= 3: 
            a=distancedata.pop(0)
        distancedata.append(float(i))
        print i,  distancedata,sum(distancedata) / len(distancedata)

The results here:

0 [0, 0, 0.0] 0.0
1 [0, 0.0, 1.0] 0.333333333333
2 [0.0, 1.0, 2.0] 1.0
3 [1.0, 2.0, 3.0] 2.0
4 [2.0, 3.0, 4.0] 3.0
5 [3.0, 4.0, 5.0] 4.0
6 [4.0, 5.0, 6.0] 5.0
7 [5.0, 6.0, 7.0] 6.0
8 [6.0, 7.0, 8.0] 7.0
9 [7.0, 8.0, 9.0] 8.0
10 [8.0, 9.0, 10.0] 9.0
11 [9.0, 10.0, 11.0] 10.0
12 [10.0, 11.0, 12.0] 11.0
13 [11.0, 12.0, 13.0] 12.0
14 [12.0, 13.0, 14.0] 13.0
15 [13.0, 14.0, 15.0] 14.0
16 [14.0, 15.0, 16.0] 15.0
17 [15.0, 16.0, 17.0] 16.0
18 [16.0, 17.0, 18.0] 17.0
19 [17.0, 18.0, 19.0] 18.0
MEdwin
  • 2,940
  • 1
  • 14
  • 27
-1

try to restrict the length of the list

_distance = []
if _distance.size == 3:
       _distance = _distance[-2:]
       _distance.append(distance)
else:
   _distance.append(distance)

 avg_distance = sum(distance[-3:]) / len(distance[-3:])
Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
  • distance in my case is a float variable, and also, i dont want to store ALL the values, because this script will run for days – Jack May 28 '19 at 09:38
  • try to restrict the length of the list ``` _distance = [] if _distance.size == 3: _distance = _distance[-2:] _distance.append(distance) else: _distance.append(distance) ``` – Arben John Avillanosa May 28 '19 at 09:46
  • Add some explanation to avoid this post to consider as low quality post by `stackoverflow`. – Harsha Biyani May 28 '19 at 11:44