@@ -833,6 +833,7 @@ and :term:`generators <generator>` which incur interpreter overhead.
833833 from collections import Counter, deque
834834 from contextlib import suppress
835835 from functools import reduce
836+ from heapq import heappush, heappushpop, heappush_max, heappushpop_max
836837 from math import comb, isqrt, prod, sumprod
837838 from operator import getitem, is_not, itemgetter, mul, neg, truediv
838839
@@ -848,11 +849,6 @@ and :term:`generators <generator>` which incur interpreter overhead.
848849 # prepend(1, [2, 3, 4]) → 1 2 3 4
849850 return chain([value], iterable)
850851
851- def running_mean(iterable):
852- "Yield the average of all values seen so far."
853- # running_mean([8.5, 9.5, 7.5, 6.5]) → 8.5 9.0 8.5 8.0
854- return map(truediv, accumulate(iterable), count(1))
855-
856852 def repeatfunc(function, times=None, *args):
857853 "Repeat calls to a function with specified arguments."
858854 if times is None:
@@ -1150,6 +1146,49 @@ and :term:`generators <generator>` which incur interpreter overhead.
11501146 return n
11511147
11521148
1149+ # ==== Running statistics ====
1150+
1151+ def running_mean(iterable):
1152+ "Average of values seen so far."
1153+ # running_mean([37, 33, 38, 28]) → 37 35 36 34
1154+ return map(truediv, accumulate(iterable), count(1))
1155+
1156+ def running_min(iterable):
1157+ "Smallest of values seen so far."
1158+ # running_min([37, 33, 38, 28]) → 37 33 33 28
1159+ return accumulate(iterable, func=min)
1160+
1161+ def running_max(iterable):
1162+ "Largest of values seen so far."
1163+ # running_max([37, 33, 38, 28]) → 37 37 38 38
1164+ return accumulate(iterable, func=max)
1165+
1166+ def running_median(iterable):
1167+ "Median of values seen so far."
1168+ # running_median([37, 33, 38, 28]) → 37 35 37 35
1169+ read = iter(iterable).__next__
1170+ lo = [] # max-heap
1171+ hi = [] # min-heap the same size as or one smaller than lo
1172+ with suppress(StopIteration):
1173+ while True:
1174+ heappush_max(lo, heappushpop(hi, read()))
1175+ yield lo[0]
1176+ heappush(hi, heappushpop_max(lo, read()))
1177+ yield (lo[0] + hi[0]) / 2
1178+
1179+ def running_statistics(iterable):
1180+ "Aggregate statistics for values seen so far."
1181+ # Generate tuples: (size, minimum, median, maximum, mean)
1182+ t0, t1, t2, t3 = tee(iterable, 4)
1183+ return zip(
1184+ count(1),
1185+ running_min(t0),
1186+ running_median(t1),
1187+ running_max(t2),
1188+ running_mean(t3),
1189+ )
1190+
1191+
11531192.. doctest ::
11541193 :hide:
11551194
@@ -1226,10 +1265,6 @@ and :term:`generators <generator>` which incur interpreter overhead.
12261265 [(0, 'a'), (1, 'b'), (2, 'c')]
12271266
12281267
1229- >>> list (running_mean([8.5 , 9.5 , 7.5 , 6.5 ]))
1230- [8.5, 9.0, 8.5, 8.0]
1231-
1232-
12331268 >>> for _ in loops(5 ):
12341269 ... print (' hi' )
12351270 ...
@@ -1789,6 +1824,28 @@ and :term:`generators <generator>` which incur interpreter overhead.
17891824 True
17901825
17911826
1827+ >>> list (running_mean([8.5 , 9.5 , 7.5 , 6.5 ]))
1828+ [8.5, 9.0, 8.5, 8.0]
1829+ >>> list (running_mean([37 , 33 , 38 , 28 ]))
1830+ [37.0, 35.0, 36.0, 34.0]
1831+
1832+
1833+ >>> list (running_min([37 , 33 , 38 , 28 ]))
1834+ [37, 33, 33, 28]
1835+
1836+
1837+ >>> list (running_max([37 , 33 , 38 , 28 ]))
1838+ [37, 37, 38, 38]
1839+
1840+
1841+ >>> list (running_median([37 , 33 , 38 , 28 ]))
1842+ [37, 35.0, 37, 35.0]
1843+
1844+
1845+ >>> list (running_statistics([37 , 33 , 38 , 28 ]))
1846+ [(1, 37, 37, 37, 37.0), (2, 33, 35.0, 37, 35.0), (3, 33, 37, 38, 36.0), (4, 28, 35.0, 38, 34.0)]
1847+
1848+
17921849.. testcode ::
17931850 :hide:
17941851
0 commit comments